/**
 * 指针
 * 普通变量存内容，指针变量存地址，二者不能互相混用，也就是，不能将普通变量的值(内容)赋给指针变量，也不能指针变量的值(地址)赋给普通变量
 */
#include "stdio.h"
#include "string.h"

int x;

/**
 *
 */
void testPointer1()
{
    int n = 10, *p = &n;
    printf("n=%d,&n=%1d\n", n, &n);
    printf("*p=%d,p=%1d\n", *p, p);
    printf("&p=%ld\n", &p);
}

void testPointer2()
{
    int n = 10, *p = &n;
    printf("n=%d,&n=%1d\n", n, &n);
    printf("*p=%d,p=%1d\n", *p, p);
    printf("&p=%ld\n", &p);

    x = *((int *) 6291036); //根据地址获取值
    printf("x=%d,n=%d,&n=%1d\n", x, n, &n);
    printf("*p=%d,p=%1d\n", *p, p);
    printf("&p=%ld\n", &p);

    p = &n;
    // p=(int *)6291036;
    printf("%d \n", p + 1);
}

/**
 * 通过指向char型指针变量p输出字符数组内容
 */
void testPoint3()
{
    char s[80] = "How are you?", *p;
    p = &s[0]; //p=s
//    printf("%d \n",p);
    while (*p)
    {
        printf("%c ", *p);
        p++; //*p++等价于*(p++)表示先取p所指数据内容，然后p指向下一个数据，相当于*p;p++;
        //(*p)++表示先取p所指向数据内容，然后将p所指向数据内容加1
    }
    printf("\n");
}

/**
 * int型指针变量p在TC下p-i所指向数据
 */
void test_pointer_data()
{
    int a = 10, b = 12, c = 14, d = 16, *p = &a;  //在栈中定义变量，则高位存储，栈是从底向上，高位在栈底（高地址)  在TC（目前不用)下则相反
    printf("*p=%d,*(p-1)=%d,*(p-2)=%d,*(p-3)=%d \n", *p, *(p - 1), *(p - 2), *(p - 3));
    printf("a=%d,b=%d,c=%d,d=%d\n", &a, &b, &c, &d);
}

/**
 * 指向char型指针变量p对应p+i所指向数据 类似于队列
 */
void test_pointer_char_data()
{
    char s[80] = "How are you?", *p;
    p = s; //p=&s[0]
    printf("%c%c%c \n", *p, *(p + 1), *(p + 2));
}


/**
 * 问题:如何通过函数swap(int a,int b)的形参实现两个变量的数据互换
 * //C语言的参数传递是单向的，即只有x,y传给a,b，没有a,b传回给x,y
 * 解决的方法：指针，即告诉子程序实参的位置，然后由子程序到该位置去修改它，改后的结果当然影响实参。
 * @return
 */
void test_swap_data(int a, int b)
{
    int t;
    t = a;
    a = b;
    b = t;  /*只是交换了x,y的副本,对x,y本身不影响*/
}

/**
 * 指针，即告诉子程序实参的位置，然后由子程序到该位置去修改它，改后的结果当然影响实参。
 * @param a
 * @param b
 */
void test_swap_data1(int *a, int *b)
{
    int t;
    t = *a;
    *a = *b;
    *b = t;
}

//传地址过来
void test_swap_data2(int *a, int *b)
{
    int t;
    t = a[0];
    a[0] = b[0];
    b[0] = t;  //地址值进行覆盖
}

/**
 * 数组与指针
 * @return
 */
//1.指向数组元素的指针定义一个指向数组元素的指针，然后让该指针指向每一个数组元素，即执行p=&a[i];，则*p=a[i]，因为*p===*&a[i]=a[i]。
//使用指向数组元素的指针输出数组元素值
void test_pointer_array_1()
{
    int a[3] = {3, 6, 9}, *p, i;
    for (i = 0; i < 3; i++)
    {
        p = &a[i];
        printf("*p=%d,a[%d]=%d\n", *p, i, a[i]);
    }
}

//2. 通过指针引用数组元素
/**
 *定义一个指针变量，然后让该指针变量指向一个一维数组，通过该指针变量来引用数组中每一个元素。
 *问题引入程序
 * 数组名代表数组的首地址
 * 数组表示形式变换为等价的指针表示形式的规则是：将数组元素a[i]中的a和i照抄，将[变为+，将]变为)，然后在a前面补上*(即为指针方式
 */
void test_pointer_array_2()
{
    int a[4] = {0, 2, 4, 6}, *p;
    p = a;//p=&a[0];
    printf("*(p+0)=%d,*(p+1)=%d,*(p+2)=%d,*(p+3)=%d\n", *(p + 0), *(p + 1), *(p + 2), *(p + 3));
    printf("(p+0)=%d,(p+1)=%d,(p+2)=%d,(p+3)=%d\n", p + 0, p + 1, p + 2, p + 3);
    printf("a[0]=%d,a[1]=%d,a[2]=%d,a[3]=%d\n", a[0], a[1], a[2], a[3]);
}

/**
 * 四个表达式a[i]、p[i]、*(p+i)、*(a+i)是互相等价的
 * @return
 */
void test_pointer_array_3()
{
    int a[5] = {1, 3, 5, 7, 9}, *p, i;
    p = a;
    for (int i = 0; i < 5; i++)
        printf("%2d,%2d,%2d,%2d\n", a[i], *(a + i), p[i], *(p + i)); //：p是指针变量，a只能叫指针常量，因可以p++或p=a,但是不能a++或a=p
}

/**
 * 指针变量p和指针常量a的应用
 * @return
 */
void test_pointer_const()
{
    int a[10] = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19}, *p, i;
    p = &a[0];
    for (i = 0; i < 10; i++)
    {
        printf("%2d ", *p++);
    }

    /* for(i=0;i<10;i++){
         printf("%2d ",*a++); //语法错误
     }*/

    printf("\n");
}

/**
 * 用数组名作函数参数
 * 定义int a[ ]={1,2,3,4,5,6,7,8,9,0},*p=a;后，p与a是等价的，说明两种定义形式即int p[ ]与int *p是等价的，
 * 因此，在函数形参中定义int p[ ]与int *p也是等价的，即出现int p[ ]的地方都可以用int *p来代替
 * @return
 */
/**
 *  定义一个函数，形参和实参都是数组方式输出数组内容
 */
void test_array_name(int p[10], int n)
{
    int i;
    for (i = 0; i < n; i++)
    {
        printf("%3d ", p[i]);
    }
    printf("\n");
}

/**
 * 定义一个函数，形参指针方式、实参数组方式输出数组内容
 */
void test_pointer_array_name(int *p, int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("%3d ", p[i]);
    }
    printf("\n");
}


//调用
void test_array_name_main()
{
    int a[10] = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19};
    //形参数组，实参数组
    test_array_name(a, 10);
    //形参指针方式、实参数组
    test_pointer_array_name(a, 10);
    //形参数组，实参指针
    int *p = a;
    test_array_name(p, 10);
    //形参和实参都是指针方式输出数组内容
    test_pointer_array_name(p, 10);

//     int *x;
//     test_pointer_array_name(x,10);

}
/**
 * 9.3.4多维数组与指针
 * 定义一个指针变量，然后让该指针变量指向一个多维数组，通过该指针变量来引用数组中每一个元素。
首先，看一下一维数组p及其等价的指针变量p的定义形式是：int p[ ]与int *p，而二维数组p的定义形式是：int p[ ][N]，因此，其等价的指针变量p的定义形式是：int (*p)[N]，如：
int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12},(*p)[4];p=a;
 */

/**
 * 指向二维数组的指针变量作函数参数
 * @return
 */
void test_array_2d_printer(int (*p)[4])
{ //*p get value by variable p that store variable a's address
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            printf("%3d ", p[i][j]);
        }
    }
    // /*p[i][j]*(p[i]+j)*(*(p+i)+j)(*(p+i))[j]*/
    printf("\n");
}

/**
 *  用指向二维数组的指针变量输出二维数组。
 */
void test_array_2d()
{
    int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
//    int (*p)[4];
//    p=a;
//    for(int i=0;i<3;i++){
//        for(int j=0;j<4;j++){
//            printf("%3d ",p[i][j]);
//        }
//    }
//    // /*p[i][j]*(p[i]+j)*(*(p+i)+j)(*(p+i))[j]*/
//    printf("\n");
    test_array_2d_printer(a);

}

/**
 * 9.4字符串与指针
 * 9.4.1字符串的表示形式
两种方法(二者区别在10.4.3介绍)：
(1)用数组存一个字符串，如char s[80]="Good!";先给s分配80单元空间然后赋初值
(2)用字符指针指向一个字符串，如char *s="OK";先分配3单元空间然后让s指向它
设有如下定义：char s[80]="Good!",*p=s;，则ps，可以出现s的地方都可以用p来代替 如:
 * @return
 */
/*scanf("%s",s);scanf("%s",p);
printf("%s",s);printf("%s",p);
scanf("%c",&s[i]);scanf("%c",&p[i]);
printf("%c",s[i]);printf("%c",p[i]);
s[i]=getchar();p[i]=getchar();*//*单字符输入*//*
putchar(s[i]);putchar(p[i]);*//*单字符输出*/

//定义一个函数strcopy(char *s2,char *s1)，实现将字符串s1内容复制到字符串s2
void strcopy(char *s2, char *s1)
{
    while (*s1 != 0)
    {
        *s2 = *s1;
        s2++;
        s1++;
    }
    *s2 = 0;
}

//简化
void strcopy1(char *s2, char *s1)
{
    while (*s2 = *s1)
    {
        s2++;
        s1++;
    }
}

//再简化
void strcopy2(char *s2, char *s1)
{ //s1->s2
    while (*s2++ = *s1++);
}

void test_pointer_string()
{
    char a[80] = "How are you?", b[80];
    /*strcopy(b,a);
    printf("copy a: %s to b: %s \n",a,b);
    strcopy1(b,a);
    printf("copy a: %s to b: %s \n",a,b);*/
    strcopy2(b, a);
    printf("copy a: %s to b: %s \n", a, b);
}

/**
 * 9.4.2字符指针作函数参数
 * 定义char s[ ]="Good!",*p=s;后，p与s是等价的，说明两种定义形式即char p[ ]与char *p是等价的，
 * 因此，在函数形参中定义char s[ ]与char *p也是等价的，即出现char s[ ]的地方都可以用char *p来代替。注意，字符串常量返回的是字符指针。
 * @return
 */
//定义一个函数，形参和实参都是数组方式输出字符数组内容
void test_string_by_array(char s[])
{
    while (*s)
    {
        printf("%c", *s++); //通过指针一个一个处理
    }
    printf("\n");
}

//定义一个函数，形参指针方式、实参数组方式输出字符数组内容
void test_string_by_pointer(char *p)
{
    while (*p)
    {
        printf("%c", *p++);
    }
    printf("\n");
}

//编写一个递归函数int Len(char *s,char *s0)，用于求字符串s中以指定字符串s0为分隔符的子串数。若s为空串，则返回0；否则返回s0在s中出现的次数加1
//运行后输入的第1行字符串作为s串，第2行字符串作为s0串，通过调用Len()函数返回子串数。
//分析：若s中找到分隔符s0，则s剩余部分的子串数加1即为总子串数，否则非空返回1、空返回0。
int Len(char *s, char *s0)
{ //返回子串s0在s串中出现的次数+1
    char *p = strstr(s, s0); //没找到p返回NULL
    if (p == NULL)
        return strlen(s) > 0; //没找到且非空返回1、空返回0
    else
    {
        p += strlen(s0);
        return 1 + Len(p, s0); //找到则返回剩余部分p的子串数加1
    }
}

/**
 * 编写一个递归函数char *Ai(char *r,char *s,int i,char *s0)，用于求字符串s中以指定字符串s0为分隔符的第i子串(i=1~n)，结果存于数组r中
 * 运行后输入的第1行字符串作为s串，第2行字符串作为s0串，通过调用Ai(r,s,i,s0)函数返回第i子串。
 * 分析：若要找的不是第1子串(i>1)，则在s剩余部分中找到第i-1子串；要找的是第1子串，若s中不含分隔符(p==NULL)，则返回s，否则返回s中分隔符前的字符串拷贝。
 * 重点： 逗号运算符
 */
char *AI(char *r, char *s, int i, char *s0)
{
    char *p = strstr(s, s0); //返回"a1,a2,...,ai,...,an"中第i个子串并存于r中
    if (i > 1)
        if (p == NULL)
        {
            return p;
        } else
        {
            return AI(r, p + strlen(s0), i - 1, s0);
        }
    else
    {
        if (p == NULL)
        {
            return s;
        } else
        {
            return *p=0,s, strcopy(r,s),*p=s0[0],r;
        }
    }
    return p;
}

/**
 * 测试
 */
void test_string_by_array_main()
{
    char s[80] = "how old are you?";
    test_string_by_array(s);
    test_string_by_pointer(s);
    char *p = s;
    test_string_by_array(p); //形参数组方式、实参指针方式输出数组内容
    test_string_by_array("hello world");
    test_string_by_pointer(p); //，形参和实参都是指针方式输出数组内容

    /* char s1[80],s0[80];
     gets(s1);gets(s0);
     printf("%d \n", Len(s1,s0));*/

    //test
    char a[80] = "dog,cat,fox", b[80] = ",", r[80];
    //printf("%s\n", strstr(a, b));
    for (int i = 0; i <= 3; i++)
        printf("%s\n", AI(r, a, i, b));
}

//9.7.2指向指针的指针:指向指针的指针就是定义一个指针变量，该指针变量所存内容是另一个指针变量地址
void test_pointer_2_pointer_main(){
    int i=3;
    int *p=&i; //*取存储地址的值
    int **pp=&p;
    printf("%d,**pp address=%d,*p的地址:%d,i的地址:%d\n",**pp,*pp,p,&i);
}
//定义指针数组p的3个元素分别指向a、b、c，再定义指针变量pp指向指针数组p(如下图所示，其中地址是假设的)，通过指针变量pp输出所有元素。
void test_pointer_2_pointer_main_1(){
    int a[3]={0,1,2};
    int b[3]={3,4,5};
    int c[3]={7,8,9};
    int *p[3],**pp,i,j;
    p[0]=a;p[1]=b;p[2]=c;pp=&p[0]; //pp=p
    for(i=0;i<3;i++){
        for(j=0;j<3;j++){
            printf("%d ",pp[i][j]);
        }
        printf("\n");
    }
}

//定义指针数组p的5个元素分别指向5个字符串常量，再定义指针变量pp指向指针数组p，通过指针变量pp输出所有字符串。
void test_pointer_2_pointer_main_2(){
    char *p[5]={"vb","c++","delphi","pb","java"};
    char **pp=p;
    for(int i=0;i<5;i++){
        printf("%s-%d-%d ",pp[i], *(pp+i),*(p+i)); //并输出每个元素的地址,地址 是一样的
    }
    printf("\n");
}

int main4()
{
    setvbuf(stdout, NULL, _IONBF, 0); //解决debug时，scanf优于printf捕获的问题

    testPointer1();
    testPointer2();
    testPoint3();
    test_pointer_data();
    test_pointer_char_data();

    //测试swap
    int x = 3, y = 4;
    printf("x=%d,y=%d\n", x, y);
    test_swap_data1(&x, &y);
    printf("x=%d,y=%d\n", x, y);

    //指针数组
    test_pointer_array_1();

    test_pointer_array_2();

    test_pointer_array_3();

    test_pointer_const();

    test_array_name_main();

    //指针和二维数组
    test_array_2d();

    //指针与字符串
    test_pointer_string();

    test_string_by_array_main();

    test_pointer_2_pointer_main();

    test_pointer_2_pointer_main_1();
    test_pointer_2_pointer_main_2();
    return 0;
}